package com.gitee.l0km.codegen.webclient;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitee.l0km.codegen.annotations.AnnotationException;
import com.gitee.l0km.codegen.annotations.AnnotationRuntimeException;
import com.gitee.l0km.codegen.annotations.Remote;
import com.gitee.l0km.codegen.annotations.Service;
import com.gitee.l0km.codegen.annotations.ServicePort;
import com.gitee.l0km.codegen.base.AnnotationUtils;
import com.gitee.l0km.codegen.base.Method;
import com.gitee.l0km.codegen.base.MethodException;
import com.gitee.l0km.codegen.base.NewSourceInfoAbstract;
import com.gitee.l0km.codegen.base.ServiceInfo;
import com.gitee.l0km.com4j.base.Assert;
import com.gitee.l0km.com4j.base.ParameterNames;
import com.gitee.l0km.com4j.base.exception.ServiceRuntimeException;

public class WebClient<T> extends NewSourceInfoAbstract<T> {
	private static final Logger logger = LoggerFactory.getLogger(WebClient.class);
	protected ServiceInfo serviceInfo;
	protected final Class<?> serviceClass;
	public final ParameterNames serviceParamTable;

	protected Map<Method, Method> method2PortMap = new HashMap<Method, Method>();
	private final Map<String, java.lang.reflect.Method> servicePorts;
	@SuppressWarnings("unchecked")
	public WebClient(Class<T> interfaceClass, Class<? extends T> refClass, Class<?> serviceClass) {
		super(interfaceClass, refClass, (Class<? extends T>) refClass.getSuperclass());
		Assert.notNull(serviceClass, "serviceClass");
		this.serviceClass = serviceClass;
		try {
			servicePorts=getServicePorts(this.serviceClass);
		} catch (DuplicatedNameMethodException e) {
			throw new ExceptionInInitializerError(e);
		}
		serviceParamTable = new ParameterNames(serviceClass);
		getImportedList().put("ServiceRuntimeException", com.gitee.l0km.com4j.base.exception.ServiceRuntimeException.class);
	}

	@Override
	protected void createMethodsNeedGenerated() {
		ArrayList<java.lang.reflect.Method> interfaceMethods = new ArrayList<java.lang.reflect.Method>(
				Arrays.asList(interfaceClass.getMethods()));
		java.lang.reflect.Method[] baseMethods = null != baseClass ? baseClass.getMethods()
				: new java.lang.reflect.Method[0];
		try {
			for (Iterator<java.lang.reflect.Method> it = interfaceMethods.iterator(); it.hasNext();) {
				java.lang.reflect.Method im = it.next();
				if(needImpl(im)||!isImplemented(baseMethods, im))
				//if(!isImplemented(baseMethods, im))
					this.methodsNeedGenerated.add(new Method(refClass.getMethod(im.getName(), im.getParameterTypes()),
							this.paramTable.getParameterNames(im.getName(), im.getParameterTypes())));
			}
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		int c = 0;
		builder.append("//classes need imported in new Class:\n");
		for (Class<?> clazz : importedList.values()) {
			builder.append("//[" + (++c) + "]\nimported ").append(clazz.getName()).append(";\n");
		}
		builder.append("public class NewClass");
		if (null != this.baseClass)
			builder.append("extends " + this.baseClass.getSimpleName());
		else
			builder.append(" implements " + this.interfaceClass.getSimpleName());
		builder.append("{\n");
		c = 0;
		builder.append("//methods thad need generated in new Class:\n");
		for (Method m : methodsNeedGenerated) {
			builder.append("//[" + (++c) + "]\n").append(m.toGenericString()).append("{}\n");
		}
		builder.append("}\n");
		return builder.toString();
	}

	@Override
	public boolean compile() {
		boolean compileOk = false;
		try {
			if (super.compile()) {
				Service service = AnnotationUtils.getServiceAnnotation(interfaceClass);
//				if (null != service)
//					addImportedClass(service.genericTypeClasses());
				addImportedClass(interfaceClass);
				serviceInfo = new ServiceInfo(service);
//				method2PortMap = createMap();
				compileOk = true;
			}
		} catch (AnnotationException e) {
			logger.error(e.toString());
		} catch (AnnotationRuntimeException e) {
			logger.error(e.toString());
		} /*catch (DuplicatedNameMethodException e) {
			logger.error(e.toString());
		} catch (NoSuchMethodException e) {
			logger.error(e.toString());
		}*/
		return compileOk;
	}

	protected Map<Method, Method> createMethod2PortMapMap() throws NoSuchMethodException {
		ServicePort portDesc;
		String portName;
		java.lang.reflect.Method portMethod;
		Map<Method, Method> method2PortMap = new HashMap<Method, Method>();
		String portPrefix = serviceInfo.getPortPrefix();
		Method m;
		StringBuilder builder;
		for (Method method : methodsNeedGenerated) {
			builder=new StringBuilder(portPrefix);
			try {
				if (needImpl(method)) {
					Remote remoteAnnotation = getRemoteAnnotation(method);
					m = getPrimitiveMethod(refClass, method);
					portDesc = m.getAnnotation(ServicePort.class);
					if (portDesc != null){
						String suffix = method.getName().substring(remoteAnnotation.primtiveName().length());
						builder.append(remoteAnnotation.primtiveName()).append(portDesc.suffix()).append(suffix);
					}
					else 
						builder.append(method.getName());
				}else{
					portDesc = method.getAnnotation(ServicePort.class);
					builder.append(method.getName());
					if (portDesc != null)
						builder.append(portDesc.suffix());
				}
			} catch (MethodException e) {
				throw new RuntimeException(e);
			}			
			portName = builder.toString();
			if ((portMethod = servicePorts.get(portName)) == null)
				throw new NoSuchMethodException(String.format("not found webservice port %s for method %s ", portName,
						method.getDocSignature()));
			method2PortMap.put(method, new Method(portMethod, getPortParameterNames(portMethod)));
		}
		return method2PortMap;
	}
	
	/**
	 * 
	 * @param method 接口中的方法
	 * @return Servcie Class中对应的方法
	 */
	protected java.lang.reflect.Method getServicePort(Method method) {
		ServicePort portDesc;
		String portName;
		String portPrefix = serviceInfo.getPortPrefix();
		StringBuilder builder;
		builder=new StringBuilder(portPrefix);
		try {
			if (needImpl(method)) {
				Remote remoteAnnotation = getRemoteAnnotation(method);
				Method m = getPrimitiveMethod(refClass, method);
				portDesc = m.getAnnotation(ServicePort.class);
				if (portDesc != null){
					String suffix = method.getName().substring(remoteAnnotation.primtiveName().length());
					builder.append(remoteAnnotation.primtiveName()).append(portDesc.suffix()).append(suffix);
				}
				else 
					builder.append(method.getName());
			}else{
				portDesc = method.getAnnotation(ServicePort.class);
				builder.append(method.getName());
				if (portDesc != null)
					builder.append(portDesc.suffix());
			}
		} catch (MethodException e) {
			throw new RuntimeException(e);
		}			
		portName = builder.toString();
		return servicePorts.get(portName);
	}
	
	private final String[] getPortParameterNames(java.lang.reflect.Method portMethod) throws NoSuchMethodException {
		return serviceParamTable.getParameterNames(portMethod.getName(), portMethod.getParameterTypes());
	}
	protected final Map<String, java.lang.reflect.Method> getServicePorts(Class<?> serviceClass)
			throws DuplicatedNameMethodException {
		java.lang.reflect.Method[] methods = serviceClass.getDeclaredMethods();
		Map<String, java.lang.reflect.Method> servicePorts = new HashMap<String, java.lang.reflect.Method>(
				methods.length << 1);
		int modifier ;
		for (java.lang.reflect.Method method : methods) {
			modifier=method.getModifiers();
			if(Modifier.isPublic(modifier)){
				if (servicePorts.keySet().contains(method.getName()))
					throw new DuplicatedNameMethodException(serviceClass, method.getName());
				servicePorts.put(method.getName(), method);
			}
		}
		return servicePorts;
	}

	/**
	 * @return serviceInfo
	 */
	public ServiceInfo getServiceInfo() {
		return serviceInfo;
	}

	/**
	 * @return method2PortMap
	 */
	public Map<Method, Method> getMethod2PortMap() {
		return method2PortMap;
	}

	/**
	 * @return serviceClass
	 */
	public Class<?> getServiceClass() {
		return serviceClass;
	}
	public boolean isServiceRuntime(Class<?> exp){
		return ServiceRuntimeException.class.isAssignableFrom(exp);
	}
	public boolean needImpl(Method method){
		return getRemoteAnnotation(method)!=null;		
	}
	public boolean needImpl(java.lang.reflect.Method method){
		return getRemoteAnnotation(method)!=null;		
	}
	public Remote getRemoteAnnotation(Method method){
		return method.getAnnotation(Remote.class);		
	}
	public Remote getRemoteAnnotation(java.lang.reflect.Method method){
		return method.getAnnotation(Remote.class);		
	}

	public boolean isGenericTypeClass(Method method, Class<?> parameterType) {
		Remote remoteAnnotation = getRemoteAnnotation(method);
		return serviceInfo.getBridgeType() == String.class 
				&& remoteAnnotation != null 
				&& remoteAnnotation.value()
				&& remoteAnnotation.genericTypeClass() == parameterType;
	}

	public Method getPrimitiveMethod(Class<?> from,Method deriveMethod) throws MethodException {
		Remote remoteAnnotation = getRemoteAnnotation(deriveMethod);
		return getMatchedGenericMethod(from, deriveMethod, serviceInfo.getTargetType(), remoteAnnotation.genericParam(),
				remoteAnnotation.genericTypeClass(),remoteAnnotation.primtiveName());
	}
}
